/*
  This file is a part of hashutils package.
  (c) 2004 Zeljko Vrba <zvrba@globalnet.hr>

  Permission is hereby granted, free of charge, to any person obtaining
  a copy of this software and associated documentation files (the
  "Software"), to deal in the Software without restriction, including
  without limitation the rights to use, copy, modify, merge, publish,
  distribute, sublicense, and/or sell copies of the Software, and to
  permit persons to whom the Software is furnished to do so, subject to
  the following conditions:

  The above copyright notice and this permission notice shall be
  included in all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sfio.h>
#include <cdt.h>
#include <mycrypt.h>
#include "main.h"
#include "cdt_line.h"

static char rcsid[] = "$Id: huniq.c 2 2007-03-29 14:43:55Z zeljko.vrba $";

const char *Main_argv0;
static int print_occur = 0, print_duplicates = 0;

/*
  Read and count the number of occurrences of each line.
*/
static Dt_t *uniq_read_file(Sfio_t *file, int halg)
{
	char *record;
	Dt_t *set;

	if(!(set = dtopen(&Cdt_LineDiscipline, Dtset))) {
		sfprintf(sfstderr, "FATAL: memory exhausted. Exiting.\n");
		exit(1);
	}

	while((record = sfgetr(file, '\n', 0))) {
		unsigned long outlen = Cdt_LineDiscipline.size;
		struct line *line, *inserted;
		int err;

		if(!(line = vmalloc(LineSet_vmpool, LINESZ(Cdt_LineDiscipline.size)))) {
			sfprintf(sfstderr, "FATAL: memory exhausted. Exiting.\n");
			exit(1);
		}

		line->n = 1;			/* use n for number of occurences */
		if((err = hash_memory(halg, (unsigned char*)record,
							  sfvalue(file), line->h, &outlen)) != CRYPT_OK) {
			sfprintf(sfstderr, "FATAL: crypto error: %s, exiting.\n",
					 error_to_string(err));
			exit(1);
		}

		if((inserted = dtinsert(set, line)) != line) {
			/* duplicate; increment number of occurences */
			++inserted->n;
			vmfree(LineSet_vmpool, line);
		}
	}
	if(sferror(file)) {
		sfprintf(sfstderr, "ERROR reading FILE: sfgetr: %s\n", strerror(errno));
		exit(1);
	}

	return set;
}

/*
  actually output lines
*/
static void uniq(Sfio_t *file, Dt_t *set, Sfio_t *out, int halg)
{
	char *record;
	char hash[256];
	struct line *line;

	if(Cdt_LineDiscipline.size > 256) {
		sfprintf(sfstderr, "FATAL: hash has more than 256 bytes (2048 bits), exiting.\n");
		exit(1);
	}

	while((record = sfgetr(file, '\n', 0))) {
		unsigned long outlen = Cdt_LineDiscipline.size;
		unsigned int linelen = sfvalue(file);
		int err;

		if((err = hash_memory(halg, (unsigned char*)record,
							  sfvalue(file), hash, &outlen)) != CRYPT_OK) {
			sfprintf(sfstderr, "FATAL: crypto error: %s, exiting.\n",
					 error_to_string(err));
			exit(1);
		}
		if(!(line = dtmatch(set, hash))) {
			sfprintf(sfstderr, "FATAL: internal error: line not in set.\n");
			abort();
		}

		if((print_duplicates && (line->n < 2)) || !line->n) continue;
		record[linelen-1] = 0;
		if(print_occur) sfprintf(out, "%6d\t%s\n", line->n, record);
		else sfprintf(out, "%s\n", record);
		line->n = 0;			/* not to be printed again */
	}
	if(sferror(file)) {
		sfprintf(sfstderr, "ERROR reading FILE: sfgetr: %s\n", strerror(errno));
		exit(1);
	}
}



/******************************************************************************
 * Implementation of 'main' interface.
 *****************************************************************************/
void Main_usage(const char *progname)
{
	sfprintf(sfstderr, "USAGE: %s [OPTION] HASH FILE\n", progname);
	sfprintf(sfstderr, "  -c             print the number of occurences of each line\n");
	sfprintf(sfstderr, "  -d             print only duplicate lines\n");
	sfprintf(sfstderr, "\n");
	
	sfprintf(sfstderr, "HASH is one of the following (displayed as HASH/byte length):\n");
	Main_print_hashes();
	exit(1);	
}

int Main_process_option(int argc, char **argv, int *arg)
{
	if(strlen(argv[*arg]) != 2) Main_usage("ERROR: invalid option.");
	switch(argv[*arg][1]) {
	case 'c': print_occur = 1; return 1;
	case 'd': print_duplicates = 1; return 1;
	}
	return 0;
}

/*
  Doesn't require files to be sorted, but must have two passes. With a command
  line option, reads all data in memory, so single pass is sufficient with
  possible risk of HUGE memory usage.
*/
void Main_process_files(const char *fname1, const char *fname2, int halg)
{
	Dt_t *set;
	Sfio_t *file = Main_file_open(fname1);
	
	if(file == sfstdin) {
		sfprintf(sfstderr, "FATAL: input can't be from stdin.\n");
		exit(1);
	}

	/* set CDT key size and create Vmlast heap */
	Cdt_LineDiscipline.size = hash_descriptor[halg].hashsize;
	if(!(LineSet_vmpool = vmopen(Vmdcmmap, Vmlast, VM_TRUST))) {
		sfprintf(sfstderr, "FATAL: can't create memory heap, exiting.\n");
		exit(1);
	}

	/*
	  we must read in set lines from both files before we know unique lines
	  for each file.
	*/
	if(!(set = uniq_read_file(file, halg))) {
		sfprintf(sfstderr, "ERROR calculating FILE hashes, exiting.\n");
		exit(1);
	}
	sfclose(file);

	/*
	  Make a 2nd pass for actually outputing lines. Must re-open a file
	  because it can be a pipe, so it can't be seek'd.
	*/
	file = Main_file_open(fname1);
	uniq(file, set, sfstdout, halg);
	sfclose(file);
}
